package com.alcohol.auth.server.support.grant.base;

import com.alcohol.auth.server.service.SysOAuthClientService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.*;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.util.*;

/**
 * 基础认证处理器抽象类
 * @author LiXinYu
 * @date 2025/11/8
 */
@Slf4j
public abstract class BaseAuthenticationProvider<T extends BaseAuthenticationToken> implements AuthenticationProvider {

    /* OAuth2授权服务 */
    private final OAuth2AuthorizationService authorizationService;
    /* token生成器 */
    private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
    /* 客户端信息服务 */
    private final SysOAuthClientService sysOAuthClientService;
    /* 认证管理器 */
    private final AuthenticationManager authenticationManager;

    /**
     * OAuth2-Base认证提供者
     * @param authorizationService 授权服务
     * @param tokenGenerator  token生成器
     * @param sysOAuthClientService 客户端信息服务
     */
    public BaseAuthenticationProvider(OAuth2AuthorizationService authorizationService,
                                      OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator,
                                      SysOAuthClientService sysOAuthClientService,
                                      AuthenticationManager authenticationManager) {
        Assert.notNull(authorizationService, "authorizationService cannot be null");
        Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
        this.authorizationService = authorizationService;
        this.tokenGenerator = tokenGenerator;
        this.sysOAuthClientService = sysOAuthClientService;
        this.authenticationManager = authenticationManager;
    }

    /**
     * 执行认证操作
     * @param authentication 认证请求
     * @return 包含凭证的完整认证对象，如果当前认证提供者无法处理传入的认证对象可能返回null
     * @throws AuthenticationException 认证失败时抛出
     * @throws OAuth2AuthenticationException 当scope无效或token生成失败时抛出
     */
    @Override
    @SuppressWarnings("unchecked")
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        T authenticationToken = (T) authentication;
        AuthorizationGrantType authorizationGrantType = authenticationToken.getGrantType();

        // 获取已认证的客户端主体
        OAuth2ClientAuthenticationToken clientPrincipal = this.getAuthenticatedClientElseThrowInvalidClient(authenticationToken);
        RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
        Assert.notNull(registeredClient, "registeredClient cannot be null");
        Set<String> authorizedScopes;
        if (!CollectionUtils.isEmpty(authenticationToken.getScopes())) {
            for (String requestedScope : authenticationToken.getScopes()) {
                if (!registeredClient.getScopes().contains(requestedScope)) {
                    throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE, "scope不支持！", null));
                }
            }
            authorizedScopes = new LinkedHashSet<>(authenticationToken.getScopes());
        } else {
            authorizedScopes = new LinkedHashSet<>();
        }

        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = this.buildUserToken(authenticationToken);
        log.debug("got usernamePasswordAuthenticationToken, {}", usernamePasswordAuthenticationToken);
        Authentication usernamePasswordAuthentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);

        // 生成DefaultOAuth2TokenContext
        DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
                .registeredClient(registeredClient)
                .principal(usernamePasswordAuthentication)
                .authorizationServerContext(AuthorizationServerContextHolder.getContext())
                .authorizedScopes(authorizedScopes)
                .authorizationGrantType(authorizationGrantType)
                .authorizationGrant(authenticationToken);

        // 初始化OAuth2Authorization
        OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
                .principalName(usernamePasswordAuthentication.getName())
                .authorizedScopes(authorizedScopes)
                .authorizationGrantType(authorizationGrantType);

        // 生成AccessToken
        OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
        OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
        if (Objects.isNull(generatedAccessToken)) {
            throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, "AccessToken生成失败！", null));
        }

        if (log.isTraceEnabled()) {
            log.trace("Generated access token");
        }

        OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, generatedAccessToken.getTokenValue(),
                generatedAccessToken.getIssuedAt(), generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
        if (generatedAccessToken instanceof ClaimAccessor) {
            authorizationBuilder.token(accessToken, (metadata) ->
                    metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims()));
        } else {
            authorizationBuilder.accessToken(accessToken);
        }

        // 生成RefreshToken
        OAuth2RefreshToken refreshToken = null;
        if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
                !clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {
            tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
            OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
            if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
                throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, "RefreshToken生成失败！", null));
            }

            if (log.isTraceEnabled()) {
                log.trace("Generated refresh token");
            }

            refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
            authorizationBuilder.refreshToken(refreshToken);
        }

        // 保存OAuth2Authorization
        this.authorizationService.save(authorizationBuilder.build());

        if (log.isTraceEnabled()) {
            log.trace("Saved authorization");
        }

        // 组装客户端附加信息
        Map<String, Object> clientAdditionalParameters = sysOAuthClientService.getClientAdditionalParameters(registeredClient.getClientId());
        Map<String, Object> additionalParameters = ((BaseAuthenticationToken) authentication).getAdditionalParameters();
        Map<String, Object> originalMap = Collections.unmodifiableMap(additionalParameters);
        Map<String, Object> modifiableMap = new HashMap<>(originalMap);
        if (!CollectionUtils.isEmpty(modifiableMap)) {
            modifiableMap.putAll(clientAdditionalParameters);
        }

        if (log.isTraceEnabled()) {
            log.trace("Authenticated token request");
        }

        return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, modifiableMap);
    }

    /**
     * 获取已认证的客户端主体，否则抛出无效客户端异常
     * @param authentication 认证信息
     * @return 已认证的客户端主体
     * @throws OAuth2AuthenticationException 客户端未认证时抛出异常
     */
    private OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
        OAuth2ClientAuthenticationToken clientPrincipal = null;

        if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
            clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
        }

        if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
            return clientPrincipal;
        }

        throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, "无效客户端！", null));
    }

    /**
     * 构建用户名密码认证令牌
     * @param authenticationToken 认证信息
     * @return 用户名密码认证令牌
     */
    public abstract UsernamePasswordAuthenticationToken buildUserToken(T authenticationToken);
}
